package com.hyjy.music.widget

import android.content.Context
import android.graphics.*
import androidx.appcompat.widget.AppCompatTextView
import com.hyjy.music.bean.Lrc
import com.hyjy.music.R
import android.util.AttributeSet
import android.util.Log
import android.util.LruCache
import android.view.MotionEvent
import com.hyjy.music.getLrcList
import com.hyjy.music.util.getColorWithAlpha

typealias OnLrcViewScrollListener = Long.() -> Unit

/**
 * 歌词View
 */
class LrcView : AppCompatTextView {
    companion object {
        private const val TAG = "LrcView"
        private const val LRC_NOT_EXIST = "暂无歌词"
        private const val ALPHA_LRC = 0.618F // 普通歌词透明度
        private const val COEFFICIENT = 2.0F // lineHeight与lrcSize的比值
        private const val COEFFICIENT_LRC_SIZE = 1.25F // 高亮歌词和普通歌词字体大小比值
    }

    private var lrcSize = 0F // 歌词字体大小
    private var lineHeight = 0F // 行高
    private var changeGap = 0F
    private lateinit var defaultPaint: Paint // 默认画笔
    private lateinit var highlightPaint: Paint // 高亮歌词画笔
    private var textBounds: Rect = Rect()
    private var clipRectF: RectF = RectF()
    private var width1 = 0
    private var height1 = 0

    // 歌词集合
    private var lyrics: List<Lrc> = mutableListOf()

    // 高亮行歌词的索引
    private var lightIndex = 0

    // 歌词缓存使用LruCache(最近最少使用),防止缓存中数据太多导致内存溢出
    private val lrcCache = LruCache<String, List<Lrc>>(7)
    private var url: String? = null

    private var currentPosition: Long = 0 // 当前歌曲播放的位置
    private var totalDuration: Long = 0 // 歌曲的总时间
    private var isUniform = false // 是否匀速滚动歌词
    private var lastY = 0.0f

    constructor(context: Context) : super(context) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        init()
    }

    /**
     * 初始化
     */
    private fun init() {
        changeGap = resources.getDimension(R.dimen.size_change_gap)
        lrcSize = resources.getDimension(R.dimen.size_default)
        lineHeight = COEFFICIENT * lrcSize
        defaultPaint = Paint().apply {
            color = Color.WHITE
            textSize = lrcSize
            isAntiAlias = true // 抗锯齿
            textAlign = Paint.Align.LEFT // 设置文本对齐方式
        }

        highlightPaint = Paint().apply {
            color = Color.WHITE
            textSize = COEFFICIENT_LRC_SIZE * lrcSize
            isAntiAlias = true // 抗锯齿
            textAlign = Paint.Align.LEFT // 设置文本对齐方式
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldWidth: Int, oldHeight: Int) {
        super.onSizeChanged(w, h, oldWidth, oldHeight)
        width1 = w
        height1 = h
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (lyrics.isEmpty()) {
            LRC_NOT_EXIST.run {
                highlightPaint.getTextBounds(this, 0, this.length, textBounds)
                val y = height1 * 0.5f + textBounds.height() * 0.5f
                drawTextInCenter(canvas, this, y, highlightPaint)
            }
            return
        }

        // 滚动歌词
        if (isUniform) {
            canvas.translate(0F, -getSingedPercent() * lineHeight)
        }

        // 先绘制高亮行歌词，得到高亮行歌词的y坐标
        // 高亮行歌词包含两部分,高亮部分和未高亮部分,实现歌词渐进效果
        var text = lyrics[lightIndex].content
        defaultPaint.textSize = COEFFICIENT_LRC_SIZE * lrcSize // 先设置成大字体画出高亮行未高亮的部分
        defaultPaint.getTextBounds(text, 0, text.length, textBounds)
        val textHeight = textBounds.height() // 好像不是很精确
        val y = 0.5F * height1 + 0.5F * textHeight
        val textWidth = defaultPaint.measureText(text)
        val x = 0.5F * (width1 - textWidth)
        // 开始画未高亮部分
        canvas.drawText(text, x, y, defaultPaint)
        defaultPaint.textSize = lrcSize // 恢复原始尺寸

        // 开始画高亮部分
        canvas.save()
        // clipRectF.set(x, y - lineHeight, x + textWidth * singedPercent, y),这样设置text底部有一点点没有高亮
        clipRectF.set(x, y - textHeight, x + textWidth * getSingedPercent(), y + textHeight)
        canvas.clipRect(clipRectF)
        canvas.drawText(text, x, y, highlightPaint)
        canvas.restore()

        // 再绘制高亮行之前的歌词，根据y坐标向前偏移
        for (i in 0 until lightIndex) {
            text = lyrics[i].content
            val preY = y - (lightIndex - i) * lineHeight
            drawTextInCenter(canvas, text, preY, defaultPaint)
        }
        // 最后绘制高亮行之后的歌词，根据y坐标向后偏移
        for (i in lightIndex + 1 until lyrics.size) {
            text = lyrics[i].content
            val nextY = y + (i - lightIndex) * lineHeight
            drawTextInCenter(canvas, text, nextY, defaultPaint)
        }
    }

    /**
     * 滚动歌词
     */
    fun rollLrcView(
        url: String,
        currentPosition: Long,
        totalDuration: Long
    ) {
        this.currentPosition = currentPosition
        this.totalDuration = totalDuration

        if (url != this.url) {
            Log.d(TAG, "换一首歌播放了")
            setLrcUrl(url)
        }
        if (lyrics.isEmpty()) {
            postInvalidate()
        } else {
            // 1.根据歌曲播放的position计算高亮歌词的行数
            lightIndex = getLightLrcIndex()
            // 2.刷新重绘
            postInvalidate()
        }
    }

    /**
     * 设置歌词路径
     *
     * @param url 歌词的路径
     */
    fun setLrcUrl(url: String) {
        this.url = url
        lyrics = lrcCache[url] ?: emptyList()
        if (lyrics.isEmpty()) {
            Log.d(TAG, "解析歌词")
            lyrics = url.getLrcList()
            lrcCache.put(url, lyrics)
        } else {
            Log.d(TAG, "歌词有缓存,无需解析")
        }
        lightIndex = 0 // 防止角标越界
    }

    /**
     * 计算高亮行歌词的索引
     * currentPosition>=当前歌词的startTime,并且小于下一行歌词的startTime
     */
    private fun getLightLrcIndex(): Int {
        if (lyrics.isEmpty()) {
            return 0
        }
        var index = 0
        var startTime: Long
        for (i in lyrics.indices) {
            startTime = lyrics[i].startTime
            if (i == lyrics.size - 1) {
                // 只需要判断currentPosition否是大于当前startTime
                if (currentPosition >= startTime) {
                    index = i
                }
            } else if (i < lyrics.size - 1) {
                // 获取下一行歌词的startTime
                val newStartTime = lyrics[i + 1].startTime
                if (currentPosition in startTime until newStartTime) {
                    index = i
                }
            }
        }
        return index
    }

    // 当前歌词已经唱了多少百分比
    fun getSingedPercent(): Float {
        return kotlin.runCatching {
            // 在当前歌词的时间内,向上滚动一个行高的距离
            val lrc = lyrics[lightIndex]
            // 前后两个的时间差即歌唱的时间,最后一个特殊处理
            val lightTime: Long = if (lightIndex == lyrics.size - 1) {
                totalDuration - lrc.startTime
            } else {
                lyrics[lightIndex + 1].startTime - lrc.startTime
            }
            // 已经唱了多少时间
            val singedTime = currentPosition - lrc.startTime
            // 百分比
            singedTime.toFloat() / lightTime.toFloat()
        }.onFailure {
            Log.d(TAG, it.toString())
        }.getOrDefault(0F)
    }

    /**
     * 绘制一行居中的文本
     */
    private fun drawTextInCenter(canvas: Canvas, text: String, y: Float, paint: Paint) {
        // 计算文本x坐标
        val textWidth = paint.measureText(text)
        val x = 0.5F * (width1 - textWidth)
        canvas.drawText(text, x, y, paint)
    }

    fun setUniform(uniform: Boolean) {
        isUniform = uniform
    }

    /**
     * 设置歌词显示的颜色
     */
    fun setLrcColor(color: Int) {
        defaultPaint.color = color.getColorWithAlpha(ALPHA_LRC)
        highlightPaint.color = color
        postInvalidate()
    }

    /**
     * 设置歌词字体大小
     */
    fun setLrcSize(size: Int) {
        val normal = resources.getDimension(R.dimen.size_default)
        lrcSize = normal + size * changeGap
        lineHeight = COEFFICIENT * lrcSize
        defaultPaint.textSize = lrcSize
        highlightPaint.textSize = COEFFICIENT_LRC_SIZE * lrcSize
        postInvalidate()
    }

    /**
     * 获取实时歌词文本(用于桌面歌词显示)
     */
    fun getCurrentLrc(): Pair<String, String> {
        Log.d(TAG, "获取实时歌词文本")
        return if (lyrics.isEmpty()) {
            Pair(LRC_NOT_EXIST, "")
        } else {
            val firstLine = lyrics[lightIndex].content
            var secondLine = ""
            if (lightIndex + 1 < lyrics.size) {
                secondLine = lyrics[lightIndex + 1].content
            }
            Pair(firstLine, secondLine)
        }
    }

    private var onLrcViewScroll: OnLrcViewScrollListener? = null

    fun setOnLrcViewScrollListener(onLrcViewScroll: OnLrcViewScrollListener) {
        this.onLrcViewScroll = onLrcViewScroll
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (lyrics.isEmpty()) {
            return super.onTouchEvent(event)
        }
        when (event.action) {
            MotionEvent.ACTION_DOWN -> lastY = event.y
            MotionEvent.ACTION_MOVE -> {
                val moveY = event.y - lastY
                // 歌词滚动速度跟手势滑动的距离正相关,除以3让这种变化缓和一点
                val change = (moveY / lineHeight).toInt()
                if (change == 0) {
                    return super.onTouchEvent(event)
                }
                if (lightIndex == lyrics.size - 1 && change < 0) {
                    // 已经是最后一行,并且是向上滑
                    return super.onTouchEvent(event)
                }
                if (lightIndex == 0 && change > 0) {
                    // 已经是第一行,并且是向下滑
                    return super.onTouchEvent(event)
                }
                lightIndex -= change
                if (lightIndex < 0) {
                    lightIndex = 0
                } else if (lightIndex >= lyrics.size) {
                    lightIndex = lyrics.size - 1
                }
                val startTime = lyrics[lightIndex].startTime
                this.onLrcViewScroll?.invoke(startTime)
                // 不用实时刷新了,因为170行已经在刷新了
                // postInvalidate() // 非匀速滚动时,修复滑动歌词不及时刷新的问题
                lastY = event.y
            }
            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> lastY = 0.0f
        }
        return true
    }

    fun getLightIndex(): Int = lightIndex
}